﻿/*
 * ©2009-2010 XELF
 * http://xelf.info
 */

using System;
using System.Collections.Generic;
#if XNA || WINDOWS_PHONE
using Microsoft.Xna.Framework;
using XnaMathHelper = Microsoft.Xna.Framework.MathHelper;
using Microsoft.Xna.Framework.Content;
using _Matrix = Microsoft.Xna.Framework.Matrix;
#else
using XnaMathHelper = XELF.Framework.MathHelper;
using QuaternionHelper = XELF.Framework.Quaternion;
#endif

//#if XNA

namespace XELF.Framework {

	/// <summary>
	/// 姿勢（回転と位置）
	/// </summary>
	public struct Spatial {
		/// <summary>
		/// 位置
		/// </summary>
		public Vector3 Position;
		/// <summary>
		/// 回転
		/// </summary>
		public Quaternion Rotation;
		/// <summary>
		/// 変換行列
		/// </summary>
		public Matrix Transform {
			get {
				Matrix transform;
				Matrix.CreateFromQuaternion(ref Rotation, out transform);
				transform.Translation = Position;
				return transform;
			}
		}
	}
	/// <summary>
	/// アニメーションのキー
	/// </summary>
	public struct AnimationKey {
		/// <summary>
		/// 時間
		/// </summary>
		public float Time;
		/// <summary>
		/// 姿勢
		/// </summary>
		public Spatial Pose;
	}

	/// <summary>
	/// アニメーションのキーリスト
	/// </summary>
	public class AnimationKeys {
		/// <summary>
		/// コンストラクタ
		/// </summary>
		public AnimationKeys() {
			Weight = 1;
		}

		/// <summary>
		/// ボーンの名前
		/// </summary>
		public string Bone { get; set; }
		/// <summary>
		/// モーションブレンディングの重みづけ
		/// </summary>
		public float Weight { get; set; }

		/// <summary>
		/// キーリストを設定する。
		/// </summary>
		/// <param name="keys"></param>
		public void SetKeys(List<AnimationKey> keys) {
			Keys = keys.ToArray();
		}
		/// <summary>
		/// 文字列表現を得る。
		/// </summary>
		/// <returns></returns>
		public override string ToString() {
			return string.Format("AnimationKeys{{Bone={0}}}", Bone);
		}

		[ContentSerializer]
		AnimationKey[] Keys;
		[ContentSerializer]
		int currentIndex;
		/// <summary>
		/// キーのリスト
		/// </summary>
		public AnimationKey[] Items { get { return Keys; } }

		void GetQuadIndices(int index, out int i0, out int i1, out int i2, out int i3) {
			var i = index + Count;
			i0 = (i - 1) % Count;
			i1 = (i) % Count;
			i2 = (i + 1) % Count;
			i3 = (i + 2) % Count;
		}

		int Count { get { return Keys.Length; } }

		/// <summary>
		/// 時間長
		/// </summary>
		public float Duration { get; set; }

		/// <summary>
		/// 時間における補完された姿勢を取得する。
		/// </summary>
		/// <param name="time"></param>
		/// <returns></returns>
		public Spatial this[float time] {
			get {
				currentIndex = Math.Min(currentIndex, Keys.Length - 1);
				for (; currentIndex >= 0; currentIndex--) {
					if (time >= Keys[currentIndex].Time) {
						break;
					}
				}
				currentIndex = Math.Max(currentIndex, 0);
				for (; currentIndex < Keys.Length; currentIndex++) {
					if (time < Keys[currentIndex].Time) {
						currentIndex--;
						break;
					}
				}

				int i0, i1, i2, i3;
				GetQuadIndices(currentIndex, out i0, out i1, out i2, out i3);

				float amount;
				float time0 = Keys[i1].Time;
				if (i2 > i1) { // | ... i0 i1 i2 i3 ... |
					var t = time - time0;
					if (t < 0) t = 0;
					amount = (time - time0) / (Keys[i2].Time - time0);
				} else { // | i2 i3 ... i0 i1 . |
					amount = (time - time0) / ((Duration - time0) + Keys[i2].Time);
				}
				amount = XnaMathHelper.Clamp(amount, 0, 1);

				Spatial result;

				var r0 = Keys[i0].Pose.Rotation;
				var r1 = Keys[i1].Pose.Rotation;
				var r2 = Keys[i2].Pose.Rotation;
				var r3 = Keys[i3].Pose.Rotation;
				if (r0.W < 0) Quaternion.Negate(ref r0, out r0);
				float d;
				Quaternion.Dot(ref r0, ref r1, out d);
				if (d < 0) Quaternion.Negate(ref r1, out r1);
				Quaternion.Dot(ref r1, ref r2, out d);
				if (d < 0) Quaternion.Negate(ref r2, out r2);
				Quaternion.Dot(ref r2, ref r3, out d);
				if (d < 0) Quaternion.Negate(ref r3, out r3);

				QuaternionHelper.CatmullRom(
					ref r0,
					ref r1,
					ref r2,
					ref r3, amount, out result.Rotation);
				Vector3.CatmullRom(
					ref Keys[i0].Pose.Position,
					ref Keys[i1].Pose.Position,
					ref Keys[i2].Pose.Position,
					ref Keys[i3].Pose.Position, amount, out result.Position);

				return result;
			}
		}
	}

}

//#endif
